#!/usr/bin/python

from intelcas_manage import IntelcasManage
from bcache_manage import BcacheManage
from utils import _derror, _exec_system, _exec_shell1, _lock_file, Exp, _lock_file1, _unlock_file1,\
                 _dwarn
import os
import errno


class CommonCache(object):
    def __init__(self, node):
        self.node = node
        self.config = node.config
        self.cache_type = self.config.cache_type

        self.intelcas = IntelcasManage(node)
        self.bcache = BcacheManage(node)

        self.cache_log = '%s/cache.log' % (self.config.log_path)
        if not os.path.exists(self.cache_log):
            _exec_system('mkdir -p %s' % self.config.log_path)
            os.system("touch %s" % (self.cache_log))

    def is_cluster_stopped(self):
        #cluster must be stopped when you use cache
        cmd = "lich list"
        out, err = _exec_shell1(cmd, p=False)
        for line in out.strip().split('\n'):
            if "meta" in line or "admin" in line or "normal" in line:
                return False

        return True

    def is_running_coredev(self, dev):
        if self.cache_type == 'intelcas':
            return self.intelcas.is_running_coredev(dev)
        elif self.cache_type == 'bcache':
            return self.bcache.is_coredev(dev)
        else:
            return False

    def is_running_cachedev(self, dev):
        if self.cache_type == 'intelcas':
            return self.intelcas.is_running_cachedev(dev)
        elif self.cache_type == 'bcache':
            return self.bcache.is_cachedev(dev)
        else:
            return False

    def is_valid_cachedev(self, dev):
        if self.cache_type == 'intelcas':
            return False
        elif self.cache_type == 'bcache':
            return self.bcache.is_valid_bcachedev(dev)
        else:
            return False

    def get_cacheinfo_by_coredev(self, dev):
        if self.cache_type == 'intelcas':
            cacheid, cachedev, cachestatus, cachemode = self.intelcas.get_cacheinfo_by_coredev(dev)
        elif self.cache_type == 'bcache':
            cacheid, cachedev, cachestatus, cachemode = self.bcache.get_cacheinfo_by_coredev(dev)
        else:
            return None, None, None, None
        return cacheid, cachedev, cachestatus, cachemode

    def get_cacheinfo_by_cachedev(self, dev):
        if self.cache_type == 'intelcas':
            return self.intelcas.get_cacheinfo_by_cachedev(dev)
        elif self.cache_type == 'bcache':
            return self.bcache.get_cacheinfo_by_cachedev(dev)
        return None, None, None

    def get_mappingdev_by_coredev(self, coredev):
        if self.cache_type == 'intelcas':
            mappingdev = self.intelcas.get_mappingdev_by_coredev(coredev)
        elif self.cache_type == 'bcache':
            mappingdev = self.bcache.get_mappingdev_by_coredev(coredev)
            if mappingdev is not None and self.bcache._check_mappingdev_exists(mappingdev):
                return mappingdev
            else:
                return None
        else:
            raise Exp(errno.EINVAL, 'invalid cache type: %s' % self.cache_type)
        return mappingdev

    def get_coredev_by_fastdev(self, fastdev):
        if self.cache_type == 'intelcas':
            coredev = self.intelcas.get_coredev_by_fastdev(fastdev)
        elif self.cache_type == 'bcache':
            coredev = self.bcache.get_coredev_by_fastdev(fastdev)
        else:
            _derror('invalid cache_type %s' % self.cache_type)
            return None
        return coredev

    def get_softdisk_by_dev(self, dev):
        list_disk = os.listdir(self.config.disk_path)

        for softdisk in list_disk:
            abs_ln = os.path.join(self.config.disk_path, softdisk)
            target = os.readlink(abs_ln)
            if target == dev:
                return softdisk

        return None

    def bindcache(self, cachedev, coredev, pool, force):
        cache_policy = {"cache_mode":None, "sequential_cutoff":None, "writeback_percent":None}

        cache_policy['cache_mode'] = self.get_cache_policy(pool, "mode")
        if self.cache_type == 'intelcas':
            self.intelcas.bind_cache(cachedev, coredev, cache_policy['mode'], force)
        elif self.cache_type == 'bcache':
            cache_policy['sequential_cutoff'] = self.get_cache_policy(pool, "sequential_cutoff")
            cache_policy['writeback_percent'] = self.get_cache_policy(pool, "writeback_percent")
            self.bcache.bind_cache(cachedev, coredev, cache_policy, force)
        else:
            raise Exp(errno.EINVAL, 'invalid cache type: %s' % self.cache_type)

    def get_cset_uuid(self, dev):
        if self.cache_type == 'bcache':
            return self.bcache.get_cset_uuid_by_dev(dev)
        else:
            return None
            # raise Exp(errno.EINVAL, 'invalid cache type: %s' % self.cache_type)

    def attach_device(self, cset_uuid, dev):
        if self.cache_type == 'bcache':
            return self.bcache.attach_device(cset_uuid, dev)
        else:
            raise Exp(errno.EINVAL, 'invalid cache type: %s' % self.cache_type)

    def check_and_register(self, dev):
        if self.cache_type == 'bcache':
            return self.bcache.check_and_register(dev)
        else:
            raise Exp(errno.EINVAL, 'invalid cache type: %s' % self.cache_type)

    def remove_ln(self, sourcedev):
        softdisk = self.get_softdisk_by_dev(sourcedev)
        if softdisk is not None:
            abs_softdisk = os.path.join(self.config.disk_path, softdisk)
            _exec_rm = 'rm -f %s' % abs_softdisk
            _exec_shell1(_exec_rm, p=False)

    def delcachedev(self, cachedev, flush):
        _lock_file("/var/run/delcache_%s.lock" % cachedev[5:])

        cache_type = self.config.cache_type
        if cache_type == 'intelcas':
            self.intelcas.del_cachedev(cachedev, flush)
        elif cache_type == 'bcache':
            list_coredev = self.bcache.list_coredevs_by_cachedev(cachedev)
            if len(list_coredev) != 0:
                for coredev in list_coredev:
                    #  get_mappingdev_by_coredev must be called before del_coredev
                    self.bcache.del_coredev(coredev, flush)

            self.bcache.del_cachedev(cachedev, list_coredev, flush)
        else:
            raise Exp(errno.EINVAL, 'invalid cache type: %s' % self.cache_type)

    def delcoredev_dangerously(self, coredev):
        _lock_file("/var/run/delcore_%s.lock" % coredev[5:])

        if self.cache_type == 'bcache':
            mappingdev = self.bcache.get_mappingdev_by_coredev(coredev)
            if mappingdev is None:
                return errno.EINVAL

            self.bcache.del_coredev_dangerously(coredev)
        else:
            raise Exp(errno.EINVAL, 'invalid cache type: %s' % self.cache_type)

    def delcoredev_safe(self, coredev):
        _lock_file("/var/run/delcore_%s.lock" % coredev[5:])

        if self.cache_type == 'intelcas':
            intelcasdev = self.intelcas._get_intelcasdev_by_coredev(coredev)
            if intelcasdev is None:
                return errno.EINVAL

            self.intelcas.del_coredev(coredev, 1)
        elif self.cache_type == 'bcache':
            mappingdev = self.bcache.get_mappingdev_by_coredev(coredev)
            if mappingdev is None:
                return errno.EINVAL

            self.bcache.del_coredev(coredev, 1)
        else:
            raise Exp(errno.EINVAL, 'invalid cache type: %s' % self.cache_type)

    def delcoredev(self, coredev, flush):
        if flush:
            self.delcoredev_safe(coredev)
        else:
            self.delcoredev_dangerously(coredev)

    def list_coredevs_by_cachedev(self, cachedev):
        if self.cache_type == 'intelcas':
            return self.intelcas.list_coredevs_by_cachedev(cachedev)
        elif self.cache_type == 'bcache':
            return self.bcache.list_coredevs_by_cachedev(cachedev)
        else:
            raise Exp(errno.EINVAL, 'invalid cache type: %s' % self.cache_type)

    def set_cache_mode(self, dev, cache_mode, is_flush):
        _lock_file("/var/run/setcachemode_%s.lock" % dev[5:])

        if self.cache_type == 'intelcas':
            self.intelcas.set_cache_mode(dev, cache_mode, is_flush)
        elif self.cache_type == 'bcache':
            self.bcache.set_cache_mode(dev, cache_mode)
        else:
            raise Exp(errno.EINVAL, 'invalid cache type: %s' % self.cache_type)

    def set_cache_seq_cutoff(self, dev, seq_cutoff):
        _lock_file("/var/run/setcachemode_%s.lock" % dev[5:])

        if self.cache_type == 'intelcas':
            pass #intelcas do not support sequential_cutoff setting
        elif self.cache_type == 'bcache':
            self.bcache.set_cache_seq_cutoff(dev, seq_cutoff)
        else:
            raise Exp(errno.EINVAL, 'invalid cache type: %s' % self.cache_type)

    def scan_cache_log(self):
        fd = _lock_file1("/var/run/cache_log.lock")
        cmd = "cat %s" % self.cache_log
        out, err = _exec_shell1(cmd, p=False)
        _unlock_file1(fd)

        for line in out.strip().split('\n'):
            if len(line) == 0:
                continue

            dev = line.split(' ')[1]
            is_flush = line.split(' ')[2]
            if line.startswith("delcachedev"):
                self.delcachedev(dev, is_flush)
            elif line.startswith("delcoredev"):
                self.delcoredev(dev, is_flush)
            else:
                raise Exp(errno.EPERM, "can not parse line (%s)" % (line))

    def flush_cachedev(self, cachedev):
        if self.cache_type == 'intelcas':
            self.intelcas.flush_cachedev(cachedev)
        elif self.cache_type == 'bcache':
            self.bcache.flush_cachedev(cachedev)
        else:
            raise Exp(errno.EINVAL, 'invalid cache type: %s' % self.cache_type)

    def flush_coredev(self, coredev):
        if self.cache_type == 'intelcas':
            self.intelcas.flush_coredev(coredev)
        elif self.cache_type == 'bcache':
            self.bcache.flush_coredev(coredev)
        else:
            raise Exp(errno.EINVAL, 'invalid cache type: %s' % self.cache_type)

    def get_cache_policy(self, pool, key):
        cmd = '%s --getcachepolicy %s --pool %s' % (self.config.admin, key, pool)
        try:
            out, err = _exec_shell1(cmd, p=False)
        except Exp as e:
            return None
        return out.strip()

    def reset_node_cache_policy(self):
        for pool in self.node.disk_manage.pool_manage.pool_list():
            sequential_cutoff = self.get_cache_policy(pool, "sequential_cutoff")
            if sequential_cutoff is None or len(sequential_cutoff) == 0:
                sequential_cutoff = self.config.cache_seq_cutoff

            writeback_percent = self.get_cache_policy(pool, "writeback_percent")
            if writeback_percent is None or len(writeback_percent) == 0:
                writeback_percent = self.config.cache_wb_percent

            pool_link = self.node.disk_manage.get_pool_link(pool)
            for coredev in pool_link.values():
                if self.is_running_coredev(coredev):
                    self.cacheset("writeback_percent", writeback_percent, coredev)
                    self.cacheset("sequential_cutoff", sequential_cutoff, coredev)
                    self.cacheset("writeback_delay", '5', coredev)

        #to improve cache performance
        bcache_home_dir = "/sys/fs/bcache"
        if os.path.isdir(bcache_home_dir):
            for cset_uuid in os.listdir(bcache_home_dir):
                if cset_uuid == "register_quiet" or cset_uuid == "register":
                    continue
                cmd = "echo 0 > /sys/fs/bcache/%s/congested_write_threshold_us && echo 0 > /sys/fs/bcache/%s/congested_read_threshold_us" % (cset_uuid, cset_uuid)
                _exec_shell1(cmd, p=False)

    def cacheset(self, key, value, cachedev=None):
        if self.cache_type == 'intelcas':
            _dwarn("function not implement yet.")
        elif self.cache_type == 'bcache':
            self.bcache.cacheset(key, value, cachedev)
        else:
            raise Exp(errno.EINVAL, 'invalid cache type: %s' % self.cache_type)

    def cacheget(self, key, cachedev=None):
        if self.cache_type == 'intelcas':
            _dwarn("function not implement yet.")
        elif self.cache_type == 'bcache':
            self.bcache.cacheget(key, cachedev)
        else:
            raise Exp(errno.EINVAL, 'invalid cache type: %s' % self.cache_type)
